package org.sean.framework.redis;

import org.sean.framework.util.NumberUtil;
import org.sean.framework.util.ThreadUtil;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.List;
import java.util.stream.Collectors;

import static org.sean.framework.util.NumberUtil.getRandomInValue;

/**
 * redis 基础模板
 *
 * @param <K> K
 * @param <V> V
 * @author xielei
 */
abstract class BaseRedisTemplate<K, V> extends RedisTemplate<K, V> {
    protected static final byte[] INCREMENT_KEY = "increment_key".getBytes();
    protected static final byte[] UNIQUE_KEY = "unique_key".getBytes();
    private static final int RETRY_COUNT = 1;
    /**
     * 时间差
     */
    private volatile Long delta;

    public BaseRedisTemplate() {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        setKeySerializer(stringSerializer);
        setValueSerializer(stringSerializer);
        setHashKeySerializer(stringSerializer);
        setHashValueSerializer(stringSerializer);
    }

    /**
     * 获取redis 当前时间
     *
     * @return 时间戳
     */
    public Long getCurrentTime() {
        return getCurrentTime(RETRY_COUNT);
    }

    /**
     * 获取当前时间
     *
     * @return 时间戳
     */
    private Long getCurrentTime(int retry) {
        if (delta == null) {
            Long time = execute((RedisCallback<Long>) RedisServerCommands::time);
            if (time == null && retry <= RETRY_COUNT) {
                ThreadUtil.sleep(5);
                return getCurrentTime(++retry);
            } else {
                if (time == null) {
                    delta = null;
                } else {
                    delta = time - System.currentTimeMillis();
                }
                return time;
            }
        } else {
            return System.currentTimeMillis() + delta;
        }
    }

    /**
     * 获取时间(redis 获取不到,取本地时间)
     *
     * @return 时间戳
     */
    public long getUnsafeCurrentTime() {
        Long time = getCurrentTime();
        if (time == null) {
            time = System.currentTimeMillis();
        }
        return time;
    }

    /**
     * @return 顺序唯一值
     */
    public long getOrderUniqueValue() {
        return getOrderUniqueValue(1);
    }

    /**
     * @param delta 差值,步长
     * @return 顺序唯一值
     */
    public long getOrderUniqueValue(long delta) {
        return getNextValue(INCREMENT_KEY, getCurrentTime(), delta, 0);
    }

    /**
     * @param key   key
     * @param delta 差值,步长
     * @return 顺序唯一值
     */
    public long getOrderUniqueValue(String key, long delta) {
        return getNextValue(key, getCurrentTime(), delta, 0);
    }

    /**
     * @param key       key
     * @param initValue 初始值
     * @param delta     差值,步长
     * @return 顺序唯一值
     */
    public long getOrderUniqueValue(String key, Long initValue, long delta) {
        return getNextValue(key, initValue, delta, 0);
    }

    /**
     * @param key            key
     * @param initValue      初始值
     * @param delta          差值,步长
     * @param timeoutSeconds 超时时间,单位秒
     * @return 顺序唯一值
     */
    public long getOrderUniqueValue(String key, Long initValue, long delta, long timeoutSeconds) {
        return getNextValue(key, initValue, delta, timeoutSeconds);
    }

    /**
     * @return 随机唯一值
     */
    public String getRandomUniqueValue() {
        return getRandomUniqueValues(1).get(0);
    }

    /**
     * @param count 数量
     * @return 随机唯一值
     */
    public List<String> getRandomUniqueValues(int count) {
        return getNextValues(UNIQUE_KEY, 1000 * 1000L, getRandomInValue(1000) + 1L, 0, count)
                .stream().map(NumberUtil::to62RadixString).collect(Collectors.toList());
    }


    /**
     * @param key            Key
     * @param initValue      初始值
     * @param delta          差值
     * @param timeoutSeconds 过期时间,单位秒
     * @return 下一个值
     */
    public Long getNextValue(String key, Long initValue, long delta, long timeoutSeconds) {
        return getNextValues(key.getBytes(), initValue, delta, timeoutSeconds, 1).get(0);
    }

    /**
     * @param key            Key
     * @param initValue      初始值
     * @param delta          差值
     * @param timeoutSeconds 过期时间,单位秒
     * @return 下一个值
     */
    public Long getNextValue(byte[] key, Long initValue, long delta, long timeoutSeconds) {
        return getNextValues(key, initValue, delta, timeoutSeconds, 1).get(0);
    }

    /**
     * 获取下一个值
     *
     * @param key            Key
     * @param initValue      初始值
     * @param delta          差值
     * @param timeoutSeconds 过期时间,单位秒
     * @param count          生成总数
     * @return 下一个值
     */
    public List<Long> getNextValues(final byte[] key, Long initValue, long delta, long timeoutSeconds, int count) {
        List<Object> commandResult = executePipelined((RedisCallback<Long>) connection -> {
            if (initValue != null) {
                connection.setNX(key, initValue.toString().getBytes());
            }
            int c = count;
            while (c-- > 0) {
                connection.incrBy(key, delta);
            }
            if (timeoutSeconds > 0) {
                connection.expire(key, timeoutSeconds);
            }
            return null;
        });
        return commandResult.stream()
                .filter(e -> e instanceof Long)
                .map(e -> (Long) e)
                .collect(Collectors.toList());
    }

}
